package repository

import (
	"context"
	"database/sql"

	"gitee.com/hp-huiw/family-expense-api/internal/db"
	dbmodel "gitee.com/hp-huiw/family-expense-api/internal/db/model"
	jet "github.com/go-jet/jet/v2/mysql"
)

type Role struct {
	ID          string  `db:"id"`
	Title       string  `db:"title"`
	Authority   *string `db:"authority"`
	Description *string `db:"description"`
}

type RoleRepository interface {
	Exists(ctx context.Context) (bool, error)
	Count(ctx context.Context, ids []string, authority string) (int64, error)
	QueryID(ctx context.Context, authority string) (string, error)
	QueryNotIn(ctx context.Context, authorities []string) ([]Role, error)
	Query(ctx context.Context) ([]Role, error)
	BatchInsert(ctx context.Context, models []dbmodel.Role) error
}

func NewRoleRepository(db *sql.DB) RoleRepository {
	return &roleRepository{db: db}
}

func (r *roleRepository) Exists(ctx context.Context) (bool, error) {
	return db.Exists(ctx, r.db, tbRole, nil)
}

func (r *roleRepository) Count(ctx context.Context, ids []string, authority string) (int64, error) {
	if len(ids) > 0 {
		idsExpression := db.SliceExpression(ids)

		condition := tbRole.ID.IN(idsExpression...).
			AND(tbRole.Authority.EQ(jet.String(authority)))

		return db.Count(ctx, r.db, tbRole, condition)
	}

	return -1, nil
}

// Queries the role id by authority.
// Authority. AKA role name.
func (r *roleRepository) QueryID(ctx context.Context, authority string) (string, error) {
	var query struct {
		ID string `db:"id"`
	}

	stmt := jet.
		SELECT(
			tbRole.ID.AS("id"),
		).
		FROM(
			tbRole,
		).
		WHERE(
			tbRole.Authority.EQ(jet.String(authority)),
		)

	err := stmt.QueryContext(ctx, r.db, &query)

	if err != nil {
		return "", err
	}

	return query.ID, nil
}

// QueryNotIn queries the roles not in the specified authorities.
func (r *roleRepository) QueryNotIn(ctx context.Context, authorities []string) ([]Role, error) {
	var query []struct {
		ID          string  `db:"id"`
		Title       string  `db:"title"`
		Authority   *string `db:"authority"`
		Description *string `db:"description"`
	}

	if len(authorities) > 0 {
		authoritiesExpression := db.SliceExpression(authorities)

		stmt := jet.
			SELECT(
				tbRole.ID.AS("id"),
				tbRole.Title.AS("title"),
				tbRole.Authority.AS("authority"),
				tbRole.Description.AS("description"),
			).
			FROM(
				tbRole,
			).
			WHERE(
				tbRole.Authority.NOT_IN(authoritiesExpression...),
			)

		if err := stmt.QueryContext(ctx, r.db, &query); err != nil {
			return nil, err
		}
	}

	result := make([]Role, 0, len(query))

	for _, item := range query {
		result = append(result, item)
	}

	return result, nil
}

func (r *roleRepository) Query(ctx context.Context) ([]Role, error) {
	var query []struct {
		ID          string  `db:"id"`
		Title       string  `db:"title"`
		Authority   *string `db:"authority"`
		Description *string `db:"description"`
	}

	stmt := jet.
		SELECT(
			tbRole.ID.AS("id"),
			tbRole.Title.AS("title"),
		).
		FROM(
			tbRole,
		)

	if err := stmt.QueryContext(ctx, r.db, &query); err != nil {
		return nil, err
	}

	result := make([]Role, 0, len(query))

	for _, item := range query {
		result = append(result, item)
	}

	return result, nil
}

func (r *roleRepository) BatchInsert(ctx context.Context, models []dbmodel.Role) error {
	stmt := tbRole.
		INSERT().
		MODELS(models)

	_, err := stmt.ExecContext(ctx, r.db)

	return err
}

type roleRepository struct {
	db *sql.DB
}
